package language.monkey.laxer

import language.monkey.token.Token
import language.monkey.token.TokenType
import kotlin.test.Test
import kotlin.test.assertNotNull

class TestLaxer {

    @Test
    fun testNextToken() {
        val input = """
            let five = 5;
            let ten = 10;

            let add = fn(a, b) {
               a + b;
            };

            let result = add(five, ten);
            
            !-/*5;
            5 < 10 > 5;
            
            if (5 < 10) {
               return true;
            } else {
               return false;
            }
            
            10 == 10;
            10 != 9;
        """.trimIndent()

        val lexer = Lexer(input)

        val tests = arrayOf(
            Token(TokenType.LET, "let"),
            Token(TokenType.IDENT, "five"),
            Token(TokenType.ASSIGN, "="),
            Token(TokenType.INT, "5"),
            Token(TokenType.SEMICOLON, ";"),

            Token(TokenType.LET, "let"),
            Token(TokenType.IDENT, "ten"),
            Token(TokenType.ASSIGN, "="),
            Token(TokenType.INT, "10"),
            Token(TokenType.SEMICOLON, ";"),

            Token(TokenType.LET, "let"),
            Token(TokenType.IDENT, "add"),
            Token(TokenType.ASSIGN, "="),
            Token(TokenType.FUNCTION, "fn"),
            Token(TokenType.LPAREN, "("),
            Token(TokenType.IDENT, "a"),
            Token(TokenType.COMMA, ","),
            Token(TokenType.IDENT, "b"),
            Token(TokenType.RPAREN, ")"),

            Token(TokenType.LBRACE, "{"),
            Token(TokenType.IDENT, "a"),
            Token(TokenType.PLUS, "+"),
            Token(TokenType.IDENT, "b"),
            Token(TokenType.SEMICOLON, ";"),
            Token(TokenType.RBRACE, "}"),
            Token(TokenType.SEMICOLON, ";"),

            Token(TokenType.LET, "let"),
            Token(TokenType.IDENT, "result"),
            Token(TokenType.ASSIGN, "="),
            Token(TokenType.IDENT, "add"),
            Token(TokenType.LPAREN, "("),
            Token(TokenType.IDENT, "five"),
            Token(TokenType.COMMA, ","),
            Token(TokenType.IDENT, "ten"),
            Token(TokenType.RPAREN, ")"),
            Token(TokenType.SEMICOLON, ";"),

            Token(TokenType.BANG, "!"),
            Token(TokenType.MINUS, "-"),
            Token(TokenType.SLASH, "/"),
            Token(TokenType.ASTERISK, "*"),
            Token(TokenType.INT, "5"),
            Token(TokenType.SEMICOLON, ";"),

            Token(TokenType.INT, "5"),
            Token(TokenType.LT, "<"),
            Token(TokenType.INT, "10"),
            Token(TokenType.GT, ">"),
            Token(TokenType.INT, "5"),
            Token(TokenType.SEMICOLON, ";"),

            Token(TokenType.IF, "if"),
            Token(TokenType.LPAREN, "("),
            Token(TokenType.INT, "5"),
            Token(TokenType.LT, "<"),
            Token(TokenType.INT, "10"),
            Token(TokenType.RPAREN, ")"),
            Token(TokenType.LBRACE, "{"),
            Token(TokenType.RETURN, "return"),
            Token(TokenType.TRUE, "true"),
            Token(TokenType.SEMICOLON, ";"),
            Token(TokenType.RBRACE, "}"),

            Token(TokenType.ELSE, "else"),
            Token(TokenType.LBRACE, "{"),
            Token(TokenType.RETURN, "return"),
            Token(TokenType.FALSE, "false"),
            Token(TokenType.SEMICOLON, ";"),
            Token(TokenType.RBRACE, "}"),

            Token(TokenType.INT, "10"),
            Token(TokenType.EQ, "=="),
            Token(TokenType.INT, "10"),
            Token(TokenType.SEMICOLON, ";"),

            Token(TokenType.INT, "10"),
            Token(TokenType.NOT_EQ, "!="),
            Token(TokenType.INT, "9"),
        )

        assertTokenTests(tests, lexer)
    }
}

private fun assertTokenTests(tests: Array<Token>, lexer: Lexer) {
    for (test in tests) {
        val token = lexer.nextToken()
        assertNotNull(token, "Expected token to not be null")
        assert(token.type == test.type && token.literal == test.literal) { "Expected token type ${test}, but got $token" }
    }
}